package com.qyer.commons.mc;

import com.qyer.commons.mc.reloader.Reloader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class MutableCache<T> implements Runnable {

  private final int MIN_SLEEP_INTERVAL = 5000;
  private static final Logger LOGGER = LoggerFactory.getLogger(MutableCache.class);

  private String name;
  private String desc;
  private String lastUpdateTime;

  private Thread currentThread;

  private AtomicBoolean run = new AtomicBoolean(true);

  private int reloadInterval;

  public MutableCache(String name, String desc, int reloadInterval, Reloader<T> reloader) {
    this.name = name;
    this.desc = desc;
    this.reloadInterval =
      (reloadInterval < MIN_SLEEP_INTERVAL) ? MIN_SLEEP_INTERVAL : reloadInterval;
    this.reloader = reloader;
    LOGGER.info("[MC] - name - {}", name);
    LOGGER.info("[MC] - desc - {}", desc);
    LOGGER.info("[MC] - reload interval in milliseconds - {}", reloadInterval);
    LOGGER.info("[MC] - reload resource(file-path, cache-key, sql, etc.) - {}",
                reloader.getReloadResource());
  }

  public String getDesc() {
    return desc;
  }

  public String getName() {
    return name;
  }

  private Reloader<T> reloader;

  private T cacheContent;

  public T getCacheContent() {
    return cacheContent;
  }

  public void reload() {
    try {
      T t = reloader.reload();
      // fixme 应当开发一套Check接口和子类负责check返回的cache内容是否符合要求
      if (t == null) {
        LOGGER.warn("Cache content is null in this round.");
      } else {
        cacheContent = t;
        this.lastUpdateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
      }
    } catch (Exception e) {
      LOGGER.error("Cannot reload cache content.", e);
    }
  }

  public synchronized void dump() {
    LOGGER.info("Cache content: {}", cacheContent);
  }

  public void stopNow() {
    run.set(false);
    if (this.currentThread != null) {
      this.currentThread.interrupt();
    }
  }

  @Override public void run() {
    this.currentThread = Thread.currentThread();
    try {
      while (run.get() && !Thread.currentThread().isInterrupted()) {
        reload();
        LOGGER.info("Cache({}) reloaded. Success={}. Failure={}. LastUpdateTime={}", getDesc(),
                    reloader.getSuccessCnt(), reloader.getFailureCnt(), this.lastUpdateTime);
        TimeUnit.MILLISECONDS.sleep(reloadInterval);
      }
    } catch (InterruptedException e) {
      LOGGER.info("({}) is interrupted.", getDesc());
    }
    LOGGER.info("Exit reloading(Cache={}, TotalSuccess={}. TotalFailure={}", getDesc(),
                reloader.getSuccessCnt(), reloader.getFailureCnt());
  }

}
